package in.co.codedoc.struts;

import in.co.codedoc.json.JSONArrayValue;
import in.co.codedoc.json.JSONObjectValue;
import in.co.codedoc.json.JSONValue;

import java.io.Writer;
import java.util.Iterator;

import org.apache.struts2.StrutsException;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;

import com.opensymphony.xwork2.util.ValueStack;

@StrutsTag(name="json-for-each", tldTagClass="in.co.codedoc.struts.JSONForEachTag", description="JSONForEach tag.")
public class JSONForEach
	extends JSONStrutsComponentBase
{

	public JSONForEach(ValueStack stack)
	{
		super(stack);
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean start(Writer writer)
	{
		if(iterator == null)
		{
			iterator = MakeIterator();
			
			if(iterator == null)
			{
				super.end(writer, "");
				return false;
			}
			
			index = 0;
		}
		
		if(iterator.hasNext())
		{
			MoveToNext();			
			return true;
		}
		else
		{
			super.end(writer, "");
			return false;
		}
	}

	private void MoveToNext()
	{
		JSONValue item = iterator.next();

		SetValue(itemName, item);
		SetValue(itemIndexName, index);
		SetValue(firstItemName, index == 0);
		SetValue(lastItemName, !(iterator.hasNext()));
		
		if(item instanceof JSONObjectValue)
		{
			SetPropertyValues(itemName,(JSONObjectValue)item);
		}
	}
		
	@SuppressWarnings("unchecked")
	private Iterator MakeIterator()
	{
		final Object containerValue = stack.findValue(getContainer());
		
		if
		(
			(containerValue != null) 
			&& 
			(
				(containerValue instanceof JSONObjectValue)
				||
				(containerValue instanceof JSONArrayValue)
			)
		)
		{
			if(containerValue instanceof JSONObjectValue)
			{
				return new Iterator<JSONValue>()
				{
					@Override
					public boolean hasNext()
					{	
						return propertyIndex < propertyNames.length;
					}

					@Override
					public JSONValue next()
					{
						String propertyName = propertyNames[propertyIndex++];
						
						return ((JSONObjectValue)containerValue).GetProperty(propertyName);
					}

					@Override
					public void remove()
					{
						throw new StrutsException("Unaupported removal of items from json containers");
					}
					
					String[] propertyNames 
						= ((JSONObjectValue)containerValue).GetPropertyNames(null).toArray(new String[]{});
					
					int propertyIndex = 0;
				};
			}
			else
			{
				return new Iterator<JSONValue>()
				{
					@Override
					public boolean hasNext()
					{	
						return arrayIndex < ((JSONArrayValue)containerValue).GetNumEntries();
					}

					@Override
					public JSONValue next()
					{
						return ((JSONArrayValue)containerValue).GetEntry(arrayIndex++);
					}

					@Override
					public void remove()
					{
						throw new StrutsException("Unaupported removal of items from json containers");
					}
										
					int arrayIndex = 0;
				};
			}
		}
		else
		{
			return null;
		}
	}
	
	@Override
	public boolean end(Writer writer, String body)
	{
		UnsetValues();

		index++;
		if(iterator.hasNext())
		{
			MoveToNext();			
			return true; 
		}
		else
		{
			super.end(writer, body);
			return false;
		}
	}

	public String getContainer()
	{
		return container;
	}

	@StrutsTagAttribute(description="The reference which names the container to iterate upon", type="String", required=true)
	public void setContainer(String container)
	{
		this.container = container;
	}
	
	private String container;
	
	public String getItem()
	{
		return itemName;
	}

	@StrutsTagAttribute(description="The item which takes on the values.", type="String", required=true)
	public void setItem(String item)
	{
		this.itemName = item;
		
		String intialUCItemName = Character.toUpperCase(itemName.charAt(0)) + itemName.substring(1); 
		firstItemName = "first" + intialUCItemName;
		lastItemName = "last" + intialUCItemName;
		itemIndexName = itemName + "Index";
	}
	
	private String itemName;
	private String firstItemName;
	private String lastItemName;
	private String itemIndexName;
	
	private int index = 0;
	
	private Iterator<JSONValue> iterator;
}
